#define __STDC_LIMIT_MACROS

#include <cassert>
#include <cerrno>
#include <climits>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <limits>
#include <memory>
#include <stdexcept>
#include <fcntl.h>
#include <syslog.h>

#include "fileNode.h"
#include "extraField.h"
#include "config.h"
#include "vsphere_handle.h"
#include "huawei_handle.h"

extern struct vmfuse_param param;

const int64_t FileNode::ROOT_NODE_INDEX = -1;
const int64_t FileNode::NEW_NODE_INDEX = -2;
const int64_t FileNode::TMP_DIR_INDEX = -2;

FileNode::FileNode(const char *fname, int64_t id, std::shared_ptr<DataNode> data, const char *real_name, int real_file_type, uint64_t real_file_size, const std::vector<int>& real_file_vector, const std::unordered_map<int, std::vector<DataSegment>>& cache)
{
    full_name = fname;
    _id = id;
    _data = std::move(data);
    _real_name = real_name;
    _real_file_type = real_file_type;
    _real_file_size = real_file_size;
    _real_file_vector = real_file_vector;
    _cache = cache;
}

void FileNode::parse_name()
{
    assert(!full_name.empty());

    const char *lsl = full_name.c_str();
    while (*lsl++)
    {
    }
    lsl--;
    while (lsl > full_name.c_str() && *lsl != '/')
    {
        lsl--;
    }
    // If the last symbol in file name is '/' then it is a directory
    if (*lsl == '/' && *(lsl + 1) == '\0')
    {
        // It will produce two \0s at the end of file name. I think that
        // it is not a problem
        full_name[full_name.size() - 1] = 0;
        while (lsl > full_name.c_str() && *lsl != '/')
        {
            lsl--;
        }
    }
    // Setting short name of node
    if (*lsl == '/')
    {
        lsl++;
    }
    this->name = lsl;
}

FileNode *FileNode::createRootNode()
{
    auto data = DataNode::createNew(S_IFDIR | 0755, 0, 0, 0);
    FileNode *n = new FileNode("", ROOT_NODE_INDEX, std::move(data));
    if (n == NULL)
    {
        return NULL;
    }
    n->name = n->full_name.c_str();
    return n;
}

FileNode *FileNode::createDir(const char *fname, int64_t id, uid_t owner, gid_t group, mode_t mode) {
    assert(id >= 0);
    // FUSE does not pass S_IFDIR bit here
    auto data = DataNode::createNew(S_IFDIR | mode, owner, group, 0);
    FileNode *n = new FileNode(fname, id, std::move(data));
    if (n == NULL) {
        return NULL;
    }
    n->parse_name();
    return n;
}

int FileNode::open_disk_files(const char *real_name, std::vector<int>& fd_vector, std::vector<std::string>& cbt_vector)
{
    std::string c_full_name = real_name;
    std::string only_name = "";
    std::string only_path = "";
    size_t pos = c_full_name.find_last_of('/');
    if(pos == std::string::npos){
        only_name = c_full_name;
        only_path = "";
    } else {
        only_name = c_full_name.substr(pos+1);
        only_path = c_full_name.substr(0, pos);
    }
    if(strlen(param.backupFilelist) && strlen(param.backupDisklist) && strstr(param.backupDisklist, only_name.c_str()))
    {
        char fileNamelist[10240] = {0};
        char *c_token;
        char *saveptr;
        strcpy(fileNamelist, param.backupFilelist);
        c_token = strtok_r(fileNamelist, ",", &saveptr);
        while(c_token != NULL)
        {
            char fileName[512] = {0};
            sprintf(fileName, "%s/%s", c_token, only_name.c_str());
            int fd = ::open(fileName, O_RDONLY);
            if (fd < 0)
            {
                syslog(LOG_ERR, "failed to open %s,with %s", fileName, strerror(errno));
                fprintf(stderr, "%s:failed to open %s,with %s\n", PROGRAM, fileName, strerror(errno));
                return -1;
            }
            fd_vector.push_back(fd);
            strcat(fileName, "_cbt.json");
            std::string cbt_file = fileName;
            cbt_vector.push_back(cbt_file);
            c_token = strtok_r(NULL, ",", &saveptr);
        }
    }
    else
    {
        int fd = ::open(real_name, O_RDONLY);
        if (fd < 0)
        {
            syslog(LOG_ERR, "failed to open %s,with %s", real_name, strerror(errno));
            fprintf(stderr, "%s:failed to open %s,with %s\n", PROGRAM, real_name, strerror(errno));
            return -1;
        }
        fd_vector.push_back(fd);
    }
    if(fd_vector.empty())
    {
        syslog(LOG_ERR, "not found any disk file to open %s", real_name);
        fprintf(stderr, "%s:not found any disk file to open %s\n", PROGRAM, real_name);
        return -1;
    }
    //std::reverse(fd_vector.begin(), fd_vector.end());
    return static_cast<int>(fd_vector.size());
}

std::vector<DataSegment> FileNode::scan_file(std::string cbt_file) {
    std::vector<DataSegment> segments;
    json_t *json_config = NULL;
    process_json_work_file(cbt_file.c_str(), &json_config);
    segments = get_cbtinfo(json_config);
    if (json_config)
    {
        json_decref(json_config);
    }
    if (segments.empty())
    {
        syslog(LOG_ERR, "not found any data segment in %s", cbt_file.c_str());
        fprintf(stderr, "%s:not found any data segment in %s\n", PROGRAM, cbt_file.c_str());
        return segments;
    }
    //zfs文件系统通过lseek读取的有效数据段不准确，所以不使用lseek读取有效数据段
    /*
    off_t offset = 0;
    while (offset < filesize) {
        off_t start = lseek(fd, offset, SEEK_DATA);
        if (start == (off_t)-1) {
            if (errno == ENXIO) break;
            syslog(LOG_ERR, "lseek SEEK_DATA failed: %s", strerror(errno));
            break;
        }

        off_t end = lseek(fd, start, SEEK_HOLE);
        if (end == (off_t)-1) {
            if (errno == ENXIO) {
                end = filesize;
            } else {
                syslog(LOG_ERR, "lseek SEEK_HOLE failed: %s", strerror(errno));
                break;
            }
        }

        if (start < end) {
            segments.push_back({start, end});
            syslog(LOG_DEBUG, "found data segment: %ld - %ld", start, end);
        }

        offset = end;
    }
    */

    return segments;
}

FileNode *FileNode::createNodeForExisting(const char *shortName, const char *fname, int64_t id, int type, struct stat fileStat)
{
    assert(id >= 0);
    auto data = DataNode::createExisting(static_cast<uint64_t>(id), fileStat);
    FileNode *n = NULL;
    if (type == TYPE_VSPHERE)
    {
        std::unordered_map<int, std::vector<DataSegment>> cache;
        std::vector<int> real_file_vector;
        std::vector<std::string> cbt_file_vector;
        int real_file_count = open_disk_files(fname, real_file_vector, cbt_file_vector);
        if (real_file_count <= 0)
        {
            syslog(LOG_ERR, "createNodeForExisting:failed to open real_name %s", fname);
            fprintf(stderr, "%s:createNodeForExisting:failed to open real_name %s\n", PROGRAM, fname);
            return 0;
        }
        if (real_file_vector.size() != cbt_file_vector.size())
        {
            syslog(LOG_ERR, "createNodeForExisting:real_file_vector.size() != cbt_file_vector.size()");
            fprintf(stderr, "%s:createNodeForExisting:real_file_vector.size() != cbt_file_vector.size()\n", PROGRAM);
            return 0;
        }
        for (size_t i = 0; i < real_file_vector.size(); i++) {
            //syslog(LOG_DEBUG, "scan file %s, index %lu", fname, i);
            cache[real_file_vector[i]] = scan_file(cbt_file_vector[i]);
        }
        n = new FileNode(shortName, id, data, fname, type, static_cast<uint64_t>(fileStat.st_size), real_file_vector, cache);
    }
    else
    {
        n = new FileNode(shortName, id, data, fname, type, static_cast<uint64_t>(fileStat.st_size));
    }
    if (n == NULL)
    {
        return NULL;
    }
    n->parse_name();
    return n;
}

uint64_t FileNode::size() const
{
    return _data->size();
}

bool FileNode::is_dir() const
{
    return S_ISDIR(_data->mode());
}

void FileNode::appendChild(FileNode *child)
{
    childs.push_back(child);
    if (child->is_dir())
        ++_numDirLinks;
}

void FileNode::detachChild (FileNode *child) {
    childs.remove (child);
    if (child->is_dir())
        --_numDirLinks;
}

FileNode *FileNode::createIntermediateDir(const char *fname)
{
    auto data = DataNode::createTmpDir(S_IFDIR | 0755, 0, 0, 0);
    FileNode *n = new FileNode(fname, TMP_DIR_INDEX, std::move(data));
    if (n == NULL)
    {
        return NULL;
    }
    n->parse_name();
    return n;
}

void FileNode::chmod(mode_t mode)
{
    _data->chmod(mode);
}

void FileNode::setUid(uid_t uid)
{
    _data->setUid(uid);
}

void FileNode::setGid(gid_t gid)
{
    _data->setGid(gid);
}

void FileNode::setTimes(const timespec &atime, const timespec &mtime)
{
    _data->setTimes(atime, mtime);
}

void FileNode::setCTime (const timespec &ctime) {
    _data->setCTime(ctime);
}

int FileNode::open()
{
    return _data->open();
}

int FileNode::read(char *buf, size_t sz, size_t offset) {
    if(_real_file_type == TYPE_FILE)
    {
        return _data->file_read(buf, sz, offset, _real_name.c_str(), _real_file_size);
    }
    else if(_real_file_type == TYPE_VSPHERE)
    {
        return _data->vsphere_read(buf, sz, offset, _real_file_size,  _real_file_vector, _store_file, _cache);
    }
    else if(_real_file_type == TYPE_FSPHERE)
    {
        return _data->fsphere_read(buf, sz, offset, _real_name.c_str(), _real_file_size, _store_name.c_str());
    }
    else{
        return _data->read(buf, sz, offset);
    }
}

int FileNode::write(const char *buf, size_t sz, size_t offset) {
    if(_real_file_type == TYPE_FILE)
    {
        return _data->file_write(buf, sz, offset, _real_name.c_str(), _real_file_size);
    }
    else if(_real_file_type == TYPE_VSPHERE)
    {
        return _data->vsphere_write(buf, sz, offset, _real_file_size, _real_file_vector, _store_file, _cache);
    }
    else if(_real_file_type == TYPE_FSPHERE)
    {
        return _data->fsphere_write(buf, sz, offset, _real_name.c_str(), _real_file_size, _store_name.c_str());
    }
    else{
        return _data->write(buf, sz, offset);
    }
}

int FileNode::close() {
    return _data->close();
}

FileNode *FileNode::createFile (const char *fname, uid_t owner, gid_t group, mode_t mode, dev_t dev) {
    auto data = DataNode::createNew(mode, owner, group, dev);
    FileNode *n = new FileNode(fname, NEW_NODE_INDEX, std::move(data));
    if (n == NULL) {
        return NULL;
    }
    n->parse_name();
    return n;
}

FileNode *FileNode::createSymlink(const char *fname) {
    auto data = DataNode::createNew(S_IFLNK | 0777, 0, 0, 0);
    FileNode *n = new FileNode(fname, NEW_NODE_INDEX, std::move(data));
    if (n == NULL) {
        return NULL;
    }
    n->parse_name();

    return n;
}

int FileNode::truncate(size_t offset) {
    assert(_data);
    return _data->truncate(offset);
}

void FileNode::rename(const char *new_name) {
    full_name = new_name;
    _data->touchCTime();
    parse_name();
}

FileNode::~FileNode()
{
    if(!_real_file_vector.empty())
    {
        for(int fd : _real_file_vector)
        {
            if(fd > 0)
                ::close(fd);
        }
    }
    if(_store_file > 0)
    {
        ::close(_store_file);
    }
}